using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using DragonAwakening.Core.World;
using DragonAwakening.Core.Game;
using DragonAwakening.Core.Graphics;

namespace DragonAwakening.Game
{
    /// <summary>
    /// Game instance class represent current game state.
    /// </summary>
    public class GameInstance : IGameContext
    {
        /// <summary>
        /// Game instance.
        /// </summary>
        private static GameInstance instance;

        /// <summary>
        /// Game state.
        /// </summary>
        private GameState state = new GameState();

        /// <summary>
        /// Character list.
        /// </summary>
        private List<Character> characters = new List<Character>(4);

        /// <summary>
        /// Position
        /// </summary>
        public Point position = Point.Zero;

        /// <summary>
        /// Direction
        /// </summary>
        public Sides direction = Sides.North;

        /// <summary>
        /// Game world.
        /// </summary>
        public World world = new World();

        /// <summary>
        /// Fight or dialog active opponents.
        /// </summary>
        public FightStatus FightStatus;

        private int stepElapsedTime;

        /// <summary>
        /// Gets current game instance.
        /// </summary>
        public static GameInstance Create()
        {
            return new GameInstance();
        }

        /// <summary>
        /// Map of walls to check of the cell offset (north normalized)
        /// </summary>
        private KeyValuePair<Sides, Point>[] AdjacentOffsets = new KeyValuePair<Sides, Point>[]
        { 
            new KeyValuePair<Sides, Point>(Sides.South, new Point(0, 1)),  // N
            new KeyValuePair<Sides, Point>(Sides.West, new Point(1, 0)),   // E
            new KeyValuePair<Sides, Point>(Sides.North, new Point(0, -1)), // S 
            new KeyValuePair<Sides, Point>(Sides.East, new Point(-1, 0)),  // W
            new KeyValuePair<Sides, Point>(Sides.South, new Point(1, 1)),  // NE  
            new KeyValuePair<Sides, Point>(Sides.North, new Point(1, -1)), // SE  
            new KeyValuePair<Sides, Point>(Sides.North, new Point(-1, -1)),// SW  
            new KeyValuePair<Sides, Point>(Sides.South, new Point(-1, 1))  // NW  
        };

        /// <summary>
        /// Views container.
        /// </summary>
        public Dictionary<Type, object> viewsContainer = new Dictionary<Type, object>();

        /// <summary>
        /// Gets the character list.
        /// </summary>
        public List<Character> Group
        {
            get { return this.characters; }
        }

        /// <summary>
        /// Gets the game state
        /// </summary>
        public GameState CurrentState
        {
            get { return this.state; }
        }

        /// <summary>
        /// Constructs game instance
        /// </summary>
        private GameInstance()
        {
        }

        /// <summary>
        /// Sets the player current position.
        /// </summary>
        /// <param name="position">Position.</param>
        public void SetPlayerCurrentPosition(Point position)
        {
            this.position = position;
        }

        /// <summary>
        /// Sets player current direction.
        /// </summary>
        /// <param name="direction">Direction.</param>
        public void SetPlayerCurrentDirection(Sides direction)
        {
            this.direction = direction;
        }

        /// <summary>
        /// 
        /// </summary>
        public void LoadMap(ContentManager content)
        {
            var mapDataProcessor = new WorldMapCellsProcessor(null, this.world);
            var mapStartProcessor = new WorldStartPointProcessor(mapDataProcessor, this.world, this);
            var mapSizeProcessor = new WorldMapSizeProcessor(mapStartProcessor, this.world);

            string mapData = content.Load<string>("world\\world");
            if (!string.IsNullOrWhiteSpace(mapData))
            {
                mapSizeProcessor.Process(mapData, 0);
            }
        }

        /// <summary>
        /// Moves the group forward.
        /// </summary>
        /// <returns>True if group moved, otherwise false.</returns>
        public bool MoveFroward()
        {
            int dx = this.direction == Sides.West ? -1 : this.direction == Sides.East ? 1 : 0;
            int dy = this.direction == Sides.North ? -1 : this.direction == Sides.South ? 1 : 0;

            var cell = world.GetCell(this.position);
            var side = SideHelper.RotateSide(Sides.North, this.direction);
            
            var isWall = cell.GetWall(side) > 0;
            if (!isWall)
            {
                this.position = this.position.Add(new Point(dx, dy));
            }

            return !isWall;
        }

        /// <summary>
        /// Updates game state
        /// </summary>
        public void UpdateGameState(List<KeyValuePair<string, float>> messageList, GameTime gameTime, int stepTime)
        {
            if (this.FightStatus == null)
            {
                CheckForCreatures(messageList);
                stepElapsedTime = stepTime;
            }
            else 
            {
                if (this.stepElapsedTime <= 0)
                {
                    this.FightStatus.ExecuteStep();
                    stepElapsedTime = stepTime;
                }
                else
                {
                    this.stepElapsedTime -= gameTime.ElapsedGameTime.Milliseconds;
                }
            }
        }

        /// <summary>
        /// Check creatures and create instance of flight.
        /// </summary>
        private void CheckForCreatures(List<KeyValuePair<string, float>> messageList)
        {
            // verify for monster presence
            var cellsWithCreatureGroups = new Dictionary<MapCell, List<Creature>>();
            foreach (var adjacentDescriptor in AdjacentOffsets)
            {
                var cell = this.world.GetCell(this.position.Add(WorldHelper.RotateOffset(adjacentDescriptor.Value, this.direction)));
                if (cell != null && cell.GetWall(adjacentDescriptor.Key) == 0 && cell.HasCreatures())
                {
                    cellsWithCreatureGroups.Add(cell, cell.Creatures.Select(c => CreatureManager.GetCreatureById(c)).ToList());
                }
            }

            bool hasAggressiveCreatures = cellsWithCreatureGroups.Values.Any(x => x.Any(c => c.IsAggressive));
            if (hasAggressiveCreatures)
            {
                //this.state = GameState.Fight;

                var groupsOfAggressiveCreatures = new List<List<Creature>>();

                // move creatures to the center of the screen.
                var frontCoordinates = this.position.Add(new Point(this.direction == Sides.East ? 1 : (this.direction == Sides.West ? -1 : 0), this.direction == Sides.South ? 1 : (this.direction == Sides.North ? -1 : 0)));
                var frontCell = this.world.GetCell(frontCoordinates);
                if (frontCell != null)
                {
                    var newCreatureList = new List<byte>(frontCell.Creatures ?? Enumerable.Empty<byte>());
                    foreach (var cellGroups in cellsWithCreatureGroups.Where(x => x.Key != frontCell))
                    {
                        var aggresiveCreatureList = cellGroups.Value.Where(x => x.IsAggressive).ToList();
                        newCreatureList.AddRange(aggresiveCreatureList.Select(x => x.Id));
                        cellGroups.Key.Creatures = cellGroups.Value.Where(x => !x.IsAggressive).Select(x => x.Id).ToArray();
                        groupsOfAggressiveCreatures.Add(aggresiveCreatureList);
                    }

                    frontCell.Creatures = newCreatureList.ToArray();
                }

                this.FightStatus = new FightStatus(groupsOfAggressiveCreatures, messageList);
                this.FightStatus.Initialize(this);
            }
        }

        /// <summary>
        /// Gets the view for the object.
        /// </summary>
        /// <typeparam name="T">Type of the object</typeparam>
        public IScreenView<T> GetView<T>()
        {
            object view = null;
            this.viewsContainer.TryGetValue(typeof(T), out view);
            return view as IScreenView<T>;
        }

        /// <summary>
        /// Tries to set the game state.
        /// </summary>
        /// <param name="state">Game state to be set.</param>
        /// <returns>True if state has been changed, otherwise false.</returns>
        public bool SetState(GameState state)
        {
            this.state = state;
            return true;
        }
    }
}
